// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
package com.leadingsoft.test.dialog;

import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;

import org.cef.network.CefPostData;
import org.cef.network.CefPostDataElement;
import org.cef.network.CefRequest;
import org.cef.network.CefRequest.CefUrlRequestFlags;

@SuppressWarnings("serial")
public class UrlRequestDialog extends JDialog {
	private final Vector<JRadioButton> requestMethods = new Vector<>();
	private final Map<JCheckBox, Integer> requestFlags = new HashMap<>();
	private final TableModel headerTblModel = new TableModel(true);
	private final TableModel postDataModel = new TableModel(false);
	private final JTextField urlField;
	private final JTextField cookieUrl;
	private final Frame owner_;

	private CefRequest createRequest() {
		String url = urlField.getText();
		if (url.isEmpty() || url.trim().equalsIgnoreCase("http://")) {
			SwingUtilities.invokeLater(new Runnable() {
				@Override
				public void run() {
					JOptionPane.showMessageDialog(owner_,
							"Please specify at least an URL. Otherwise the CefRequest is invalid");
				}
			});
			return null;
		}
		CefRequest request = CefRequest.create();
		if (request == null)
			return null;
		String firstPartyForCookie = cookieUrl.getText();
		if (firstPartyForCookie.isEmpty() || firstPartyForCookie.trim().equalsIgnoreCase("http://"))
			firstPartyForCookie = url;
		String method = "GET";
		for (int i = 0; i < requestMethods.size(); i++) {
			JRadioButton button = requestMethods.get(i);
			if (button.isSelected()) {
				method = button.getText();
				break;
			}
		}
		CefPostData postData = null;
		int postDataRows = postDataModel.getRowCount();
		if (postDataRows > 0) {
			postData = CefPostData.create();
		} else if (method.equalsIgnoreCase("POST") || method.equalsIgnoreCase("PUT")) {
			SwingUtilities.invokeLater(new Runnable() {
				@Override
				public void run() {
					JOptionPane.showMessageDialog(owner_, "The methods POST and PUT require at least one row of data.");
				}
			});
			return null;
		}
		if (postData != null) {
			for (int i = 0; i < postDataRows; i++) {
				String value = (String) postDataModel.getValueAt(i, 0);
				if (value.trim().isEmpty())
					continue;
				CefPostDataElement elem = CefPostDataElement.create();
				if (elem != null) {
					File f = new File(value);
					if (f.isFile()) {
						elem.setToFile(value);
					} else {
						byte[] byteStr = value.getBytes();
						elem.setToBytes(byteStr.length, byteStr);
					}
					postData.addElement(elem);
				}
			}
		}
		Map<String, String> headerMap = null;
		int headerRows = headerTblModel.getRowCount();
		if (headerRows > 0) {
			headerMap = new HashMap<>();
			for (int i = 0; i < headerRows; i++) {
				String key = (String) headerTblModel.getValueAt(i, 0);
				String value = (String) headerTblModel.getValueAt(i, 1);
				if (key.trim().isEmpty())
					continue;
				headerMap.put(key, value);
			}
		}
		int flags = 0;
		Set<Entry<JCheckBox, Integer>> entrySet = requestFlags.entrySet();
		for (Entry<JCheckBox, Integer> entry : entrySet) {
			if (entry.getKey().isSelected()) {
				flags |= entry.getValue();
			}
		}
		request.set(url, method, postData, headerMap);
		request.setFirstPartyForCookies(firstPartyForCookie);
		request.setFlags(flags);
		return request;
	}
	public UrlRequestDialog(Frame owner, String title) {
		super(owner, title, false);
		setSize(1200, 600);
		setLayout(new BorderLayout());
		owner_ = owner;
		// URL for the request
		urlField = new JTextField("http://");
		JPanel urlPanel = createPanelWithTitle("Request URL", 1, 0);
		urlPanel.add(urlField);
		// URL for the cookies
		cookieUrl = new JTextField("http://");
		JPanel cookiePanel = createPanelWithTitle("Cookie-URL", 1, 0);
		cookiePanel.add(cookieUrl);
		// Radio buttons for the request method
		ButtonGroup requestModeGrp = new ButtonGroup();
		JPanel requestModePanel = createPanelWithTitle("HTTP Request Mode", 1, 0);
		addRequestMode(requestModePanel, requestModeGrp, "GET", true);
		addRequestMode(requestModePanel, requestModeGrp, "HEAD", false);
		addRequestMode(requestModePanel, requestModeGrp, "POST", false);
		addRequestMode(requestModePanel, requestModeGrp, "PUT", false);
		addRequestMode(requestModePanel, requestModeGrp, "DELETE", false);
		// Checkboxes for the flags
		JPanel flagsPanel = createPanelWithTitle("Flags", 0, 1);
		addRequestFlag(flagsPanel, "Skip cache", CefUrlRequestFlags.UR_FLAG_SKIP_CACHE,
				"If set the cache will be skipped when handling the request", false);
		addRequestFlag(flagsPanel, "Allow cached credentials", CefUrlRequestFlags.UR_FLAG_ALLOW_CACHED_CREDENTIALS,
				"If set user name, password, and cookies may be sent with the request, "
						+ "and cookies may be saved from the response.",
				false);
		addRequestFlag(flagsPanel, "Report Upload Progress", CefUrlRequestFlags.UR_FLAG_REPORT_UPLOAD_PROGRESS,
				"If set upload progress events will be generated when a request has a body", false);
		addRequestFlag(flagsPanel, "Report RawHeaders", CefUrlRequestFlags.UR_FLAG_REPORT_RAW_HEADERS,
				"If set the headers sent and received for the request will be recorded", false);
		addRequestFlag(flagsPanel, "No download data", CefUrlRequestFlags.UR_FLAG_NO_DOWNLOAD_DATA,
				"If set the CefURLRequestClient.onDownloadData method will not be called", false);
		addRequestFlag(flagsPanel, "No retry on 5xx", CefUrlRequestFlags.UR_FLAG_NO_RETRY_ON_5XX,
				"If set 5XX redirect errors will be propagated to the observer instead of automatically re-tried.",
				false);
		// Table for header values
		JPanel headerValues = createPanelWithTable("Header Values", headerTblModel);
		headerTblModel.addEntry("User-Agent", "Mozilla/5.0 JCEF Example Agent");
		headerTblModel.addEntry("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
		// Table for post-data
		JPanel postData = createPanelWithTable("Post Data", postDataModel);
		// Create view
		JPanel headerPanel = new JPanel(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weightx = 0.5;
		c.gridx = 0;
		c.gridy = 0;
		headerPanel.add(urlPanel, c);
		c.gridx = 1;
		headerPanel.add(cookiePanel, c);
		c.gridx = 0;
		c.gridy = 1;
		c.gridwidth = 2;
		c.weightx = 0.0;
		headerPanel.add(requestModePanel, c);
		JPanel centerPanel = new JPanel(new GridLayout(2, 0));
		centerPanel.add(headerValues);
		centerPanel.add(postData);
		JButton abortButton = new JButton("Abort");
		abortButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				setVisible(false);
				dispose();
			}
		});
		JButton sendButton = new JButton("Send");
		sendButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				CefRequest request = createRequest();
				if (request == null)
					return;
				UrlRequestDialogReply handleRequest = new UrlRequestDialogReply(owner_, getTitle() + " - Result");
				handleRequest.send(request);
				handleRequest.setVisible(true);
				setVisible(false);
				dispose();
			}
		});
		JPanel bottomPanel = new JPanel(new GridLayout(0, 2));
		bottomPanel.add(abortButton);
		bottomPanel.add(sendButton);
		add(headerPanel, BorderLayout.PAGE_START);
		add(flagsPanel, BorderLayout.LINE_START);
		add(centerPanel, BorderLayout.CENTER);
		add(bottomPanel, BorderLayout.PAGE_END);
	}
	private void addRequestMode(JPanel panel, ButtonGroup buttonGrp, String requestMode, boolean selected) {
		JRadioButton button = new JRadioButton(requestMode, selected);
		buttonGrp.add(button);
		panel.add(button);
		requestMethods.addElement(button);
	}
	private void addRequestFlag(JPanel panel, String flag, int value, String tooltip, boolean selected) {
		JCheckBox checkBox = new JCheckBox(flag, selected);
		checkBox.setToolTipText(tooltip);
		panel.add(checkBox);
		requestFlags.put(checkBox, value);
	}
	private JPanel createPanelWithTitle(String title, int rows, int cols) {
		JPanel result = new JPanel(new GridLayout(rows, cols));
		result.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(title),
				BorderFactory.createEmptyBorder(10, 10, 10, 10)));
		return result;
	}
	private JPanel createPanelWithTable(String title, TableModel tblModel) {
		final TableModel localTblModel = tblModel;
		JPanel result = new JPanel(new BorderLayout());
		result.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(title),
				BorderFactory.createEmptyBorder(10, 10, 10, 10)));
		JTable table = new JTable(tblModel);
		table.setFillsViewportHeight(true);
		JScrollPane scrollPane = new JScrollPane(table);
		JPanel buttonPane = new JPanel(new GridLayout(0, 2));
		JButton addButton = new JButton("Add entry");
		addButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				localTblModel.newDefaultEntry();
			}
		});
		buttonPane.add(addButton);
		JButton delButton = new JButton("Remove entry");
		delButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				localTblModel.removeSelected();
			}
		});
		buttonPane.add(delButton);
		result.add(scrollPane, BorderLayout.CENTER);
		result.add(buttonPane, BorderLayout.PAGE_END);
		return result;
	}

	private class TableModel extends AbstractTableModel {
		private final String[] columnNames;
		private Vector<Object[]> rowData = new Vector<>();
		private final boolean hasKeyColumn_;

		public TableModel(boolean hasKeyColumn) {
			super();
			hasKeyColumn_ = hasKeyColumn;
			if (hasKeyColumn)
				columnNames = new String[] { "Key", "Value", "" };
			else
				columnNames = new String[] { "Value", "" };
		}
		public void newDefaultEntry() {
			int row = rowData.size();
			if (hasKeyColumn_) {
				Object[] rowEntry = { "key", "value", new Boolean(false) };
				rowData.addElement(rowEntry);
			} else {
				Object[] rowEntry = { "value", new Boolean(false) };
				rowData.addElement(rowEntry);
			}
			fireTableRowsInserted(row, row);
		}
		public void removeSelected() {
			int idx = hasKeyColumn_ ? 2 : 1;
			for (int i = 0; i < rowData.size(); ++i) {
				if ((Boolean) rowData.get(i)[idx]) {
					rowData.remove(i);
					fireTableRowsDeleted(i, i);
					i--;
				}
			}
		}
		public void addEntry(String key, String value) {
			int row = rowData.size();
			if (hasKeyColumn_) {
				Object[] rowEntry = { key, value, new Boolean(false) };
				rowData.addElement(rowEntry);
			} else {
				Object[] rowEntry = { value, new Boolean(false) };
				rowData.addElement(rowEntry);
			}
			fireTableRowsInserted(row, row);
		}
		@Override
		public int getRowCount() {
			return rowData.size();
		}
		@Override
		public int getColumnCount() {
			return columnNames.length;
		}
		@Override
		public String getColumnName(int column) {
			return columnNames[column];
		}
		@Override
		public Class<?> getColumnClass(int columnIndex) {
			if (rowData.size() > 0)
				return rowData.get(0)[columnIndex].getClass();
			return Object.class;
		}
		@Override
		public boolean isCellEditable(int rowIndex, int columnIndex) {
			return true;
		}
		@Override
		public Object getValueAt(int rowIndex, int columnIndex) {
			return rowData.get(rowIndex)[columnIndex];
		}
		@Override
		public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
			rowData.get(rowIndex)[columnIndex] = aValue;
			fireTableCellUpdated(rowIndex, columnIndex);
		}
	}
}
